package neuronalenetzeelearning.view.controlpanel;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayInputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import neuronalenetzeelearning.dataConnectors.GraphMLDocumentBuilder;
import neuronalenetzeelearning.model.Layer1Node;
import neuronalenetzeelearning.model.Layer2Node;
import neuronalenetzeelearning.view.IController;
import neuronalenetzeelearning.view.IView;
import neuronalenetzeelearning.view.mathoutput.MathOutputComponent;

import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.DataColorAction;
import prefuse.action.layout.SpecifiedLayout;
import prefuse.controls.Control;
import prefuse.controls.ControlAdapter;
import prefuse.controls.ZoomControl;
import prefuse.controls.ZoomToFitControl;
import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.Table;
import prefuse.data.Tuple;
import prefuse.data.event.TupleSetListener;
import prefuse.data.io.DataIOException;
import prefuse.data.io.GraphMLReader;
import prefuse.data.io.GraphMLWriter;
import prefuse.data.tuple.DefaultTupleSet;
import prefuse.data.tuple.TupleSet;
import prefuse.render.AbstractShapeRenderer;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.EdgeRenderer;
import prefuse.render.LabelRenderer;
import prefuse.render.PolygonRenderer;
import prefuse.render.Renderer;
import prefuse.util.ColorLib;
import prefuse.visual.VisualItem;
import prefuse.visual.expression.InGroupPredicate;

/**
 * Demonstration of graph editor functionality.
 * See https://sourceforge.net/forum/forum.php?thread_id=1597565&forum_id=343013
 * for a discussion about the rubberband/drag select/multiple selection of
 * nodes, while the following thread
 * https://sourceforge.net/forum/message.php?msg_id=3758973&forum_id=343013
 * contains the discussion about drawing edges interactively.
 *
 * @author <a href="http://jheer.org">jeffrey heer</a>
 * @author Aaron Barsky
 * @author Björn Kruse
 */
public class GraphTestEditor extends Display implements IView {

    private static final String graphNodesAndEdges = "nnel";
    private static final String graphNodes = "nnel.nodes";
    private static final String graphEdges = "nnel.edges";
    private static final String RUBBER_BAND = "rubberband";
    private static final String NODES = graphNodes;
    private static final String SELECTED = "sel";
    private Display graphVizPanel;
    private JPanel rightPanel;
    private MathOutputComponent matrixComponent;
    private IController controller;
    private LabelRenderer m_nodeRenderer;
    private EdgeRenderer m_edgeRenderer;
    private neuronalenetzeelearning.model.Graph dataGraph;
    private static HashMap<ControlPanelPage, GraphPanelProperty> panelList;
    private JFrame frame;

    /*
     * 1	=	create a node
     * 2	=	create an edge
     * 3	=	rename node
     */
    private static int action = 1;
    private Graph g;
    private PopupMenuTestController popup;

    public GraphTestEditor(neuronalenetzeelearning.model.Graph dataGraph) {
        super(new Visualization());
        this.dataGraph = dataGraph;

        initDataGroups();
        // -- set up visualization --
        m_vis.add(graphNodesAndEdges, g);
        // -- set up renderers --
        m_nodeRenderer = new LabelRenderer("name");
        m_nodeRenderer.setHorizontalAlignment(Constants.CENTER);
        m_nodeRenderer.setRoundedCorner(20, 20);
        m_edgeRenderer = new EdgeRenderer();

        //adding an EdgeRenderer to draw arrows
        m_edgeRenderer = new EdgeRenderer();
        m_edgeRenderer.setEdgeType(Constants.EDGE_TYPE_LINE);
        m_edgeRenderer.setArrowType(Constants.EDGE_ARROW_FORWARD);
        m_edgeRenderer.setArrowHeadSize(8, 8);
        m_edgeRenderer.setRenderType(AbstractShapeRenderer.RENDER_TYPE_DRAW_AND_FILL);

        DefaultRendererFactory rf = new DefaultRendererFactory(m_nodeRenderer);
        rf.add(new InGroupPredicate(graphEdges), m_edgeRenderer);
        m_vis.setRendererFactory(rf);

        // -- set up processing actions --

        // colors
        // combine the colour assignments into an action list
        int[] palette = new int[]{ColorLib.rgb(255, 180, 180), ColorLib.rgb(190, 190, 255)};
        ColorAction nodeTextColor = new ColorAction("nnel.nodes", VisualItem.TEXTCOLOR, ColorLib.gray(0));
        ColorAction nodeFillColor = new DataColorAction("nnel.nodes", "layer", Constants.NOMINAL, VisualItem.FILLCOLOR, palette);
        nodeFillColor.add("_hover", ColorLib.rgb(220, 200, 200));
        nodeFillColor.add(VisualItem.HIGHLIGHT, ColorLib.rgb(220, 220, 0));
        ColorAction nodeStrokeColor = new ColorAction(graphNodes, VisualItem.STROKECOLOR);

        ColorAction edgeLineColor = new ColorAction("nnel.edges", VisualItem.STROKECOLOR, ColorLib.gray(100));
        edgeLineColor.add("_hover", ColorLib.rgb(220, 100, 100));
        ColorAction edgeArrowColor = new ColorAction("nnel.edges", VisualItem.FILLCOLOR, ColorLib.gray(50));
        edgeArrowColor.add("_hover", ColorLib.rgb(220, 100, 100));

        // recolor
        ActionList recolor = new ActionList();
        recolor.add(nodeTextColor);
        recolor.add(nodeFillColor);
        recolor.add(nodeStrokeColor);
        recolor.add(edgeLineColor);
        recolor.add(edgeArrowColor);
//        m_vis.putAction("recolor", recolor);


        // create a separate action list for the layout

        ActionList layout = new ActionList(ActionList.INFINITY);
        SpecifiedLayout specifiedLayout = new SpecifiedLayout("nnel.nodes", "xCoordinate", "yCoordinate");
        layout.add(specifiedLayout);
        layout.add(recolor);
        layout.add(new RepaintAction());
        m_vis.putAction("layout", layout);

        m_vis.putAction("repaint", new RepaintAction());

//      Create the focus group
        TupleSet selectedItems = new DefaultTupleSet();
        m_vis.addFocusGroup(SELECTED, selectedItems);

//        listen for changes
        TupleSet focusGroup = m_vis.getGroup(SELECTED);
        focusGroup.addTupleSetListener(new TupleSetListener() {

            @Override
            public void tupleSetChanged(TupleSet ts, Tuple[] add, Tuple[] rem) {
                //do whatever you do with newly selected/deselected items
//                    	m_vis.cancel("layout");
                for (int i = 0; i < add.length; i++) {
                    VisualItem item = (VisualItem) add[i];
                    item.setHighlighted(true);
                }
                for (int i = 0; i < rem.length; i++) {
                    VisualItem item = (VisualItem) rem[i];
                    item.setHighlighted(false);
                }

//                        m_vis.run("layout");
            }
        });

//      Create the rubber band object for rendering on screen
        Table rubberBandTable = new Table();
        rubberBandTable.addColumn(VisualItem.POLYGON, float[].class);
        rubberBandTable.addRow();
        m_vis.add(RUBBER_BAND, rubberBandTable);
        VisualItem rubberBand = (VisualItem) m_vis.getVisualGroup(RUBBER_BAND).tuples().next();
        rubberBand.set(VisualItem.POLYGON, new float[8]);
        rubberBand.setStrokeColor(ColorLib.color(ColorLib.getColor(255, 0, 0)));

//      render the rubber band with the default polygon renderer
        Renderer rubberBandRenderer = new PolygonRenderer(Constants.POLY_TYPE_LINE);
        rf.add(new InGroupPredicate(RUBBER_BAND), rubberBandRenderer);

//      Link the rubber band control to the rubber band display object
        addControlListener(new RubberBandSelect(rubberBand));


        // initialize the display
        popup = new PopupMenuTestController(m_vis, this, "eins");
        this.recalculatePositions();
        this.pan(0, 200);
//        zoom(new Point2D.Double(400, 300), 1.75);
//        addControlListener(new DragControl());
        addControlListener(new ZoomToFitControl(Control.MIDDLE_MOUSE_BUTTON));
        addControlListener(new ZoomControl());
//        addControlListener(new PanControl());
        addControlListener(popup);
//      makes us able to stop TextEditor by special KeyEvents (e.g. Enter)
        getTextEditor().addKeyListener(popup);


        this.setHighQuality(true);
        // filter graph and perform layout
        m_vis.run("layout");

        initGUI();
    }

    public neuronalenetzeelearning.model.Graph getDataGraph() {
        return dataGraph;
    }

    public void setDataGraph(neuronalenetzeelearning.model.Graph dataGraph) {
        this.dataGraph = dataGraph;
    }

    public void recalculatePositions() {

        int layer1count = dataGraph.getLayer1Nodes().size();
        int layer2count = dataGraph.getLayer2Nodes().size();

        int xLayer1 = 0;
        int xLayer2 = 0;
        int yLayer1 = 0;
        int yLayer2 = 200;

        int width = this.getWidth();

        int xStepLayer1 = width / (layer1count + 1);
        int xStepLayer2 = width / (layer2count + 1);

        for (int i = 0; i < g.getNodeCount(); i++) {
            Node node = g.getNode(i);
            if (((String) node.get("layer")).equals("Layer1Node")) {
                xLayer1 = xLayer1 + xStepLayer1;
                node.set("xCoordinate", xLayer1);
                node.set("yCoordinate", yLayer1);
            } else {
                if (((String) node.get("layer")).equals("Layer2Node")) {
                    xLayer2 = xLayer2 + xStepLayer2;
                    node.set("xCoordinate", xLayer2);
                    node.set("yCoordinate", yLayer2);
                }
            }
        }
        m_vis.run("update");
//        m_vis.run("colour");
        m_vis.run("recolour");
        m_vis.run("layout");
    }

    public static int getAction() {
        return action;
    }

    public static void setAction(int action) {
        GraphTestEditor.action = action;
    }
    public void disablePopupMenuController(){
        popup.setEnabled(false);
    }
    public void enablePopupMenuController(){
        this.popup.setEnabled(true);
    }
    public PopupMenuTestController getPopupMenueController(){
        return this.popup;
    }
    public void updatePopupMenuTestController(Visualization m_vis, Graph g, GraphTestEditor gte, Display dis){
        this.popup.setDisplay(dis);
        this.popup.setEditor(gte);
        this.popup.setGraph(g);
        this.popup.setVis(m_vis);
        this.popup.updateDummy();
    }
    public void setG(prefuse.data.Graph graph){
        g = graph;
    }
    public Graph getG(){
        return g;
    }

    private void initDataGroups() {
//        dataGraph = neuronalenetzeelearning.model.Graph.getExampleGraph();
        // 1. Load the data
        g = null;
        /* graph will contain the core data */
        try {
            String xmlString = (GraphMLDocumentBuilder.convertToGraphMLAsString(dataGraph));
            ByteArrayInputStream inputStream = new ByteArrayInputStream(xmlString.getBytes());
            System.out.println("InitDataGRoups(Editor): "+inputStream);
            g = new GraphMLReader().readGraph(inputStream);
            /* load the data from an XML file */
        } catch (DataIOException e) {
            e.printStackTrace();
            System.err.println("Error loading graph. Exiting...");
            System.exit(1);
        }
    }

    private void initGUI() {
        //Set the look and feel
        panelList = new HashMap();
        panelList.put(ControlPanelPage.DescriptionGraphPanel, new DescriptionGraphPanel(this.dataGraph));
        panelList.put(ControlPanelPage.PropertyGraphPanel, new PropertyGraphPanel());
        panelList.put(ControlPanelPage.LearningRuleChooserPanel, new LearningRuleChooserPanel());
        panelList.put(ControlPanelPage.StepFunktionPanel, new StepFunktionPanel());
        panelList.put(ControlPanelPage.LearningRuleHebbPanel, new LearningRuleHebbPanel());
        panelList.put(ControlPanelPage.LearningRuleSuperwisedLearningPanel, new LearningRuleSuperwisedLearningPanel());
        panelList.put(ControlPanelPage.StepModeChooserPanel, new StepModeChooserPanel());
        panelList.put(ControlPanelPage.StepwisePanel, new StepwisePanel());
        panelList.put(ControlPanelPage.NStepPanel, new NStepPanel());
        panelList.put(ControlPanelPage.PassPanel, new PassPanel());

        //Set the look and feel
        try {
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");//für die Darstellung der Buttons u.ä.
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(GUITestPanels.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            Logger.getLogger(GUITestPanels.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            Logger.getLogger(GUITestPanels.class.getName()).log(Level.SEVERE, null, ex);
        } catch (UnsupportedLookAndFeelException ex) {
            Logger.getLogger(GUITestPanels.class.getName()).log(Level.SEVERE, null, ex);
        }

        Controller controller = new Controller(getDataGraph(), this, panelList);

        
        recalculatePositions();
        //applying borderlayout
        graphVizPanel = this;
        rightPanel = new JPanel();

        rightPanel.add(panelList.get(ControlPanelPage.DescriptionGraphPanel));
        //setting panel's layout
        rightPanel.setBackground(Color.WHITE);
        rightPanel.setPreferredSize(new Dimension(500, 725));
        rightPanel.repaint();
        rightPanel.updateUI();
        graphVizPanel.setBackground(Color.WHITE);
        graphVizPanel.setPreferredSize(new Dimension(500, 550));
        graphVizPanel.setSize(new Dimension(500, 550));

        //set properties
        frame = new JFrame("NNEL - Graph Editor");
        frame.setLayout(new BorderLayout());
        frame.setPreferredSize(new Dimension(1024, 768));
        frame.add(graphVizPanel, BorderLayout.CENTER);
        frame.add(rightPanel, BorderLayout.EAST);
        recalculatePositions();
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
        frame.setBackground(Color.WHITE);
        frame.repaint();
    }

    /**
     * STATIC method for initializing UI as standalone
     */
    private static void initUI() {
/*
        neuronalenetzeelearning.model.Graph graph = neuronalenetzeelearning.model.Graph.getExampleGraph();
        final GraphTestEditor ed = new GraphTestEditor(graph);
        

        BorderLayout borderLayout;//Fenster wird in Himmelsrichtungen aufgeteilt
        JPanel graphVizPanel;
        JPanel rightPanel;
        HashMap<ControlPanelPage, GraphPanelProperty> panelList;
        panelList = new HashMap();
        panelList.put(ControlPanelPage.DescriptionGraphPanel, new DescriptionGraphPanel(dataGraph));
        panelList.put(ControlPanelPage.PropertyGraphPanel, new PropertyGraphPanel());
        panelList.put(ControlPanelPage.LearningRuleChooserPanel, new LearningRuleChooserPanel());
        panelList.put(ControlPanelPage.LearningRuleHebbPanel, new LearningRuleHebbPanel());
        panelList.put(ControlPanelPage.LearningRuleSuperwisedLearningPanel, new LearningRuleSuperwisedLearningPanel());
        panelList.put(ControlPanelPage.StepModeChooserPanel, new StepModeChooserPanel());
        panelList.put(ControlPanelPage.StepwisePanel, new StepwisePanel());
        panelList.put(ControlPanelPage.NStepPanel, new NStepPanel());
        panelList.put(ControlPanelPage.PassPanel, new PassPanel());

        //Set the look and feel
        try {
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");//für die Darstellung der Buttons u.ä.
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(GUITestPanels.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            Logger.getLogger(GUITestPanels.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            Logger.getLogger(GUITestPanels.class.getName()).log(Level.SEVERE, null, ex);
        } catch (UnsupportedLookAndFeelException ex) {
            Logger.getLogger(GUITestPanels.class.getName()).log(Level.SEVERE, null, ex);
        }


        Controller controller = new Controller(graph, ed, panelList);
        //applying borderlayout
        borderLayout = new BorderLayout();
        ed.setLayout(borderLayout);
        graphVizPanel = new JPanel();
        rightPanel = new JPanel();
        ed.add(graphVizPanel, BorderLayout.CENTER);
        ed.add(rightPanel, BorderLayout.EAST);

        //setting panel's layout
        rightPanel.setBackground(Color.WHITE);
        graphVizPanel.setBackground(Color.WHITE);

        rightPanel.add(panelList.get(ControlPanelPage.DescriptionGraphPanel));


        JPanel main = new JPanel(new BorderLayout());
        main.add(ed, BorderLayout.CENTER);
        main.add(rightPanel, BorderLayout.EAST);

        //set properties
        JFrame frame = new JFrame("NNEL - Graph Editor");
        frame.setPreferredSize(new Dimension(1300, 520));
        frame.setPreferredSize(new Dimension(1024, 768));
        frame.setContentPane(main);
        frame.pack();
        frame.setVisible(true);
        frame.setBackground(Color.WHITE);
*/
    }

    @Override
    public void addNode(neuronalenetzeelearning.model.Node node) {
//        Node n = g.addNode(); //create a new node
//        n.set("name", node.getName()); //assign a new name

//        n.set("layer", "Layer1Node");
//        n.set("threshold", "1.0");
//        g.addEdge(source, n); //add an edge from source to the new node

        Node newNode = g.addNode();
        //set properties of newNode to the values corresponding to parameter
        newNode.set("threshold", String.valueOf(node.getThreshold()));
        newNode.set("name", node.getName());
        if (node instanceof Layer1Node) {
            newNode.set("layer", "Layer1Node");
        } else {
            if (node instanceof Layer2Node) {
                newNode.set("layer", "Layer2Node");
            }
        }
    }

    @Override
    public IController getController() {
        return controller;
    }

    @Override
    public JComponent getGraphVizPanel() {
        return this;
    }

    public JPanel getRightPanel() {
        return rightPanel;
    }

    public void setRightPanel(JPanel rightPanel) {
        this.rightPanel = rightPanel;
    }

    public Display getLeftPanel(){
        return this;
    }

    @Override
    public void setController(IController controller) {
        this.controller = controller;
    }

    public MathOutputComponent getMatrixComponent() {
        return matrixComponent;
    }

    public void setMatrixComponent(MathOutputComponent matrixComponent) {
        this.matrixComponent = matrixComponent;
    }

    public static void main(String argv[]) {
        initUI();
    }

    void updateFrame(GraphTestEditor view, ControlPanelPage VisiblePanel) {
        frame.remove(view.getRightPanel());
        frame.add(panelList.get(VisiblePanel), BorderLayout.EAST);
        this.setRightPanel(panelList.get(VisiblePanel));
        frame.validate();
        frame.repaint();
    }

    public neuronalenetzeelearning.model.Graph getGraph(){
        return this.dataGraph;
    }

    public void setGraph(neuronalenetzeelearning.model.Graph graph){
        this.dataGraph = graph;
    }

   
//  Add a control to set the rubber band bounds and perform the actual selection logic
    public class RubberBandSelect extends ControlAdapter {

        private int downX1, downY1;
        private VisualItem rubberBand;
        Point2D screenPoint = new Point2D.Float();
        Point2D absPoint = new Point2D.Float();
        Rectangle2D rect = new Rectangle2D.Float();
        Rectangle r = new Rectangle();

        public RubberBandSelect(VisualItem rubberBand) {
            this.rubberBand = rubberBand;
        }

        @Override
        public void mousePressed(MouseEvent e) {
            if (!SwingUtilities.isLeftMouseButton(e)) {
                return;
            }

            Display d = (Display) e.getComponent();
            Visualization vis = d.getVisualization();
            TupleSet focus = vis.getFocusGroup(SELECTED);
            if (!e.isShiftDown()) {
                focus.clear();
            }

            float[] bandRect = (float[]) rubberBand.get(VisualItem.POLYGON);
            bandRect[0] = bandRect[1] = bandRect[2] = bandRect[3] =
                    bandRect[4] = bandRect[5] = bandRect[6] = bandRect[7] = 0;

            d.setHighQuality(false);
            screenPoint.setLocation(e.getX(), e.getY());
            d.getAbsoluteCoordinate(screenPoint, absPoint);
            downX1 = (int) absPoint.getX();
            downY1 = (int) absPoint.getY();
            rubberBand.setVisible(true);
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            if (!SwingUtilities.isLeftMouseButton(e)) {
                return;
            }

            Display d = (Display) e.getComponent();
            screenPoint.setLocation(e.getX(), e.getY());
            d.getAbsoluteCoordinate(screenPoint, absPoint);
            int x1 = downX1;
            int y1 = downY1;
            int x2 = (int) absPoint.getX();
            int y2 = (int) absPoint.getY();

            float[] bandRect = (float[]) rubberBand.get(VisualItem.POLYGON);
            bandRect[0] = x1;
            bandRect[1] = y1;
            bandRect[2] = x2;
            bandRect[3] = y1;
            bandRect[4] = x2;
            bandRect[5] = y2;
            bandRect[6] = x1;
            bandRect[7] = y2;

            if (x2 < x1) {
                int temp = x2;
                x2 = x1;
                x1 = temp;
            }
            if (y2 < y1) {
                int temp = y2;
                y2 = y1;
                y1 = temp;
            }
            rect.setRect(x1, y1, x2 - x1, y2 - y1);

            Visualization vis = d.getVisualization();
            TupleSet focus = vis.getFocusGroup(SELECTED);

            if (!e.isShiftDown()) {
                focus.clear();
            }

            //allocate the maximum space we could need

            Tuple[] selectedItems = new Tuple[vis.getGroup(NODES).getTupleCount()];
            Iterator it = vis.getGroup(NODES).tuples();

            //in this example I'm only allowing Nodes to be selected
            int i = 0;
            while (it.hasNext()) {
                VisualItem item = (VisualItem) it.next();
                if (item.isVisible() && item.getBounds().intersects(rect)) {
                    selectedItems[i++] = item;
                }
            }

            //Trim the array down to the actual size
            Tuple[] properlySizedSelectedItems = new Tuple[i];
            System.arraycopy(selectedItems, 0, properlySizedSelectedItems, 0, i);
            for (int j = 0; j < properlySizedSelectedItems.length; j++) {
                Tuple tuple = properlySizedSelectedItems[j];
                focus.addTuple(tuple);
            }

            rubberBand.setValidated(false);
            d.repaint();
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            if (!SwingUtilities.isLeftMouseButton(e)) {
                return;
            }
            rubberBand.setVisible(false);
            Display d = (Display) e.getComponent();

            d.setHighQuality(true);
            d.getVisualization().repaint();
        }
    }
} // end of class GraphEditor

